package battlecruiser.shooter.reader;

import battlecruiser.board.Board;
import battlecruiser.board.ObserverBoard;
import battlecruiser.board.cell.coordinate.Coordinate;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * @author Yakiv Tymoshenko
 * @since 03.10.12
 */
public class RandomTargetReader extends TargetReader {

    private static Logger LOG = Logger.getLogger(RandomTargetReader.class);

    private final Random random = new Random();
    private ObserverBoard observerBoard;

    public RandomTargetReader(ObserverBoard observerBoard) {
        this.observerBoard = observerBoard;
    }

    @Override
    public Coordinate readTarget() throws IOException {
        List<Coordinate> validCoordinates = observerBoard.getTargetableCoordinates();
        int validCoordinatesLeft = validCoordinates.size();
        Coordinate result = validCoordinatesLeft > 0 ?
                validCoordinates.get(random.nextInt(validCoordinatesLeft))
                : null;     // TODO Consider throwing an IOException instead of returning null
        LOG.debug("Target selected: " + result);
        return result;
    }

    @Override
    public Coordinate selectRandomNeighbourOnTheSameLine(List<Coordinate> damagedDecks) throws IOException {
        final List<Coordinate> neighbours = collectNeighboursOnTheSameLine(damagedDecks);
        final int size = neighbours.size();
        final Coordinate result = neighbours.get(random.nextInt(size));
        LOG.debug("Neighbour target selected: " + result);
        return result;
    }

    @Override
    public Coordinate selectRandomNeighbour(Coordinate damagedDeck) {
        final List<Coordinate> neighbours = collectNeighbours(damagedDeck);
        final int size = neighbours.size();
        final Coordinate result = neighbours.get(random.nextInt(size));
        LOG.debug("Neighbour target selected: " + result);
        return result;
    }

    private List<Coordinate> collectNeighbours(Coordinate coordinate) {
        List<Coordinate> geographicNeighbours = new ArrayList<Coordinate>(4);
        geographicNeighbours.add(upperNeighbour(coordinate, coordinate));
        geographicNeighbours.add(lowerNeighbour(coordinate, coordinate));
        geographicNeighbours.add(leftNeighbour(coordinate, coordinate));
        geographicNeighbours.add(rightNeighbour(coordinate, coordinate));
        List<Coordinate> trueGeographicNeighbours = excludeOutOfBoardCoordinates(geographicNeighbours);
        return excludeUntargetableCoordinates(trueGeographicNeighbours);
    }

    private List<Coordinate> collectNeighboursOnTheSameLine(List<Coordinate> damagedDecks) {
        List<Coordinate> candidates = new ArrayList<Coordinate>();
        Coordinate firstDeck = damagedDecks.get(0);
        Coordinate lastDeck = damagedDecks.get(damagedDecks.size() - 1);
        if (onSameVertical(firstDeck, lastDeck)) {
            candidates.add(upperNeighbour(firstDeck, lastDeck));
            candidates.add(lowerNeighbour(firstDeck, lastDeck));
        } else {
            candidates.add(leftNeighbour(firstDeck, lastDeck));
            candidates.add(rightNeighbour(firstDeck, lastDeck));
        }
        List<Coordinate> result = excludeUntargetableCoordinates(excludeOutOfBoardCoordinates(candidates));
        LOG.debug("Candidates on the same line: " + result);
        return result;
    }

    private Coordinate upperNeighbour(Coordinate first, Coordinate last) {
        Coordinate result;
        if (first.isAboveThan(last)) {
            result = new Coordinate(first.getI() - 1, first.getJ());
        } else {
            result = new Coordinate(last.getI() - 1, last.getJ());
        }
        return result;
    }

    private Coordinate lowerNeighbour(Coordinate first, Coordinate last) {
        Coordinate result;
        if (first.isAboveThan(last)) {
            result = new Coordinate(last.getI() + 1, last.getJ());
        } else {
            result = new Coordinate(first.getI() + 1, first.getJ());
        }
        return result;
    }

    private Coordinate leftNeighbour(Coordinate first, Coordinate last) {
        Coordinate result;
        if (first.isLefterThan(last)) {
            result = new Coordinate(first.getI(), first.getJ() - 1);
        } else {
            result = new Coordinate(last.getI(), last.getJ() - 1);
        }
        return result;
    }

    private Coordinate rightNeighbour(Coordinate first, Coordinate last) {
        Coordinate result;
        if (first.isLefterThan(last)) {
            result = new Coordinate(last.getI(), last.getJ() + 1);
        } else {
            result = new Coordinate(first.getI(), first.getJ() + 1);
        }
        return result;
    }

    private List<Coordinate> excludeOutOfBoardCoordinates(List<Coordinate> coordinates) {
        List<Coordinate> result = new ArrayList<Coordinate>();
        for (Coordinate coordinate : coordinates) {
            if (Board.isWithinTheBoard(coordinate)) {
                result.add(coordinate);
            }
        }
        return result;
    }

    private List<Coordinate> excludeUntargetableCoordinates(List<Coordinate> coordinates) {
        List<Coordinate> result = new ArrayList<Coordinate>();
        for (Coordinate coordinate : coordinates) {
            if (observerBoard.getTargetableCoordinates().contains(coordinate)) {
                result.add(coordinate);
            }
        }
        return result;
    }

    private boolean onSameVertical(Coordinate c1, Coordinate c2) {
        return c1.getJ() == c2.getJ();
    }
}
